﻿/*
 * Copyright (c) 2011 http://DesignBased.NET 
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the 
 * Software  * without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 
 * Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
 * A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 * 
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using T163.Open.Api.Model;

namespace T163.Open.Api
{
    internal class OAuthRequest
    {
        private List<Parameter> parameterList = new List<Parameter>();

        private OAuthRequest(Consumer consumer, AccessToken accessToken)
        {
            Parameters = new ReadOnlyCollection<Parameter>(parameterList);

            this.Consumer = consumer;
            this.AccessToken = accessToken;
        }

        /// <summary>
        /// 创建一个 OAuthRequest 实例并设置 Consumer 和 AccessToken  
        /// </summary>
        /// <param name="consumer">Consumer</param>
        /// <param name="accessToken">AccessToken</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest Create(Consumer consumer, AccessToken accessToken)
        {
            if (consumer == null || accessToken == null)
            {
                throw new ArgumentException("Consumer 和 AccessToken 不能为空。");
            }

            return new OAuthRequest(consumer, accessToken);
        }

        /// <summary>
        /// Consumer
        /// </summary>
        internal Consumer Consumer
        {
            get;
            private set;
        }

        /// <summary>
        /// Access Token.
        /// </summary>
        internal AccessToken AccessToken
        {
            get;
            private set;
        }

        /// <summary>
        /// 请求网址
        /// </summary>
        internal string RequestUrl
        {
            get;
            set;
        }

        [DefaultValue("tGoose")]
        internal string Realm
        {
            get;
            set;
        }

        /// <summary>
        /// Http 请求的方法类型
        /// </summary>
        internal HttpMethodType MethodType
        {
            get;
            set;
        }

        /// <summary>
        /// 参数的只读列表
        /// </summary>
        internal ReadOnlyCollection<Parameter> Parameters
        {
            get;
            set;
        }

        internal List<Parameter> ParameterList
        {
            get
            {
                return parameterList;
            }
            set
            {
                parameterList = value;
            }
        }

        /// <summary>
        /// 发送请求的重试次数
        /// </summary>
        internal int RetryCount
        {
            get;
            set;
        }

        /// <summary>
        /// 设置要忽略的 HTTP StatusCode 引发的异常
        /// </summary>
        internal Predicate<int> IgnoreStatusCodeCondition
        {
            get;
            set;
        }

        /// <summary>
        /// OAuth Header 字符串
        /// </summary>
        internal string AuthorizationHeader
        {
            get;
            set;
        }

        /// <summary>
        /// 代理
        /// </summary>
        internal WebProxy Proxy
        {
            get;
            set;
        }

        /// <summary>
        /// 复制一个当前实例
        /// </summary>
        /// <returns></returns>
        internal OAuthRequest Copy()
        {
            OAuthRequest newReq = new OAuthRequest(Consumer, AccessToken);
            newReq.RequestUrl = RequestUrl;
            newReq.MethodType = MethodType;
            newReq.Realm = Realm;

            foreach (Parameter p in Parameters)
            {
                newReq.ParameterList.Add(p);
            }

            newReq.RetryCount = RetryCount;
            newReq.IgnoreStatusCodeCondition = IgnoreStatusCodeCondition;
            newReq.Proxy = Proxy;
            newReq.AuthorizationHeader = AuthorizationHeader;
            return newReq;
        }
    }

    /// <summary>
    /// 扩展类
    /// </summary>
    internal static class OAuthRequestExtension
    {
        /// <summary>
        /// 设置别名
        /// </summary>
        /// <param name="req">OAuthRequest 实例</param>
        /// <param name="realm">别名</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetRealm(this OAuthRequest req, string realm)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.Realm = realm;
            return newReq;
        }

        /// <summary>
        /// 设置请求的地址
        /// </summary>
        /// <param name="req">OAuthRequest 实例</param>
        /// <param name="url">请求的地址</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetUrl(this OAuthRequest req, string url)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.RequestUrl = url;
            return newReq;
        }

        /// <summary>
        /// 设置请求的Http 方法类型
        /// </summary>
        /// <param name="req">OAuthRequest 实例</param>
        /// <param name="type">Http 方法类型</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetHttpMethod(this OAuthRequest req, HttpMethodType type)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.MethodType = type;
            return newReq;
        }

        /// <summary>
        /// 设置代理
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <param name="proxy">Http 代理</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetProxy(this OAuthRequest req, WebProxy proxy)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.Proxy = proxy;
            return newReq;
        }

        /// <summary>
        /// 添加参数
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <param name="args">Parameter</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest AddParameter(this OAuthRequest req, params Parameter[] args)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();

            foreach (Parameter p in args)
            {
                if (!string.IsNullOrEmpty(p.Value))
                    newReq.ParameterList.Add(p);
            }

            return newReq;
        }

        /// <summary>
        /// 清除参数
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest ClearParameter(this OAuthRequest req)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            newReq.ParameterList.Clear();

            return newReq;
        }

        /// <summary>
        /// 移除指定键值的参数
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <param name="parameterKey">Parameter 键值</param>
        /// <returns>OAuthRequest</returns>
        internal static OAuthRequest RemoveParameter(this OAuthRequest req, string parameterKey)
        {
            OAuthRequest r = req as OAuthRequest;
            OAuthRequest newReq = r.Copy();
            var targets = newReq.ParameterList.FindAll((p) =>
            {
                return parameterKey == p.Key;
            });

            foreach (Parameter p in targets)
            {
                newReq.ParameterList.Remove(p);
            }
            return newReq;
        }

        /// <summary>
        /// 设置请求的最大重试次数
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <param name="count">最大重试次数</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetRetryCount(this OAuthRequest req, int count)
        {
            OAuthRequest newReq = (req as OAuthRequest).Copy();
            newReq.RetryCount = count;

            return newReq;
        }

        /// <summary>
        /// 设置要忽略的 HTTP StatusCode 引发的异常 
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <param name="condition">Predicate&lt;int&gt;</param>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest SetIgnoreStatusCodeCondition(this OAuthRequest req, Predicate<int> condition)
        {
            OAuthRequest newReq = (req as OAuthRequest).Copy();
            newReq.IgnoreStatusCodeCondition = condition;

            return newReq;
        }

        /// <summary>
        /// 提交请求
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <exception cref="System.ArgumentException">ArgumentException</exception>
        /// <returns>OAuthRequest 实例</returns>
        internal static OAuthRequest CommitRequest(this OAuthRequest req)
        {
            OAuthRequest r = req as OAuthRequest;

            if (string.IsNullOrEmpty(r.RequestUrl))
            {
                throw new ArgumentException("未设置提交请求的地址");
            }

            OAuthRequest newReq = r.Copy();

            newReq.AuthorizationHeader = OAuthUtility.GenerateAuthorizationHeader(
                r.Realm,
                r.RequestUrl,
                null,
                r.MethodType,
                r.Consumer,
                r.AccessToken,
                null,
                r.Parameters);

            return newReq;
        }

        /// <summary>
        /// Http 返回信息
        /// </summary>
        /// <param name="req">OAuthRequest</param>
        /// <returns>OAuthResponse</returns>
        internal static OAuthResponse GetResponse(this OAuthRequest req)
        {
            OAuthRequest newReq;
            HttpWebRequest httpReq;
            GetResponse(req, out newReq, out httpReq);

            int count = 0;

            return RetryAccess(newReq, httpReq, ref count);
        }

        internal static Stream GetReponseStream(this OAuthRequest req)
        {
            OAuthRequest newReq;
            HttpWebRequest httpReq;
            GetResponse(req, out newReq, out httpReq);

            return httpReq.GetResponse().GetResponseStream();
        }
        
        private static void GetResponse(OAuthRequest req, out OAuthRequest newReq, out HttpWebRequest httpReq)
        {
            OAuthRequest r = req as OAuthRequest;

            newReq = r.Copy();

            if (newReq.MethodType == HttpMethodType.Get)
            {
                string newUrl = newReq.RequestUrl;
                if (newReq.Parameters.Count != 0)
                {
                    newUrl += "?";
                }
                newUrl = newUrl + string.Join("&", newReq.Parameters.Select((p) => { return p.Key + "=" + p.Value.UrlEncode(); }).ToArray());
                httpReq = WebRequest.Create(newUrl) as HttpWebRequest;
                httpReq.Method = "GET";
            }
            else
            {
                httpReq = WebRequest.Create(newReq.RequestUrl) as HttpWebRequest;
                httpReq.Method = "POST";
                httpReq.ContentType = "application/x-www-form-urlencoded";
            }

            if (newReq.Proxy != null)
            {
                httpReq.Proxy = newReq.Proxy;
            }

            httpReq.Headers["Authorization"] = newReq.AuthorizationHeader;

            if (httpReq.Method == "POST")
            {
                string para = string.Join("&", newReq.Parameters.Select((p) => { return p.Key + "=" + p.Value.UrlEncode(); }).ToArray());
                byte[] paraBytes = Encoding.ASCII.GetBytes(para);

                httpReq.ContentLength = paraBytes.Length;

                using (Stream s = httpReq.GetRequestStream())
                {
                    s.Write(paraBytes, 0, paraBytes.Length);
                    s.Flush();
                }
            }
        }
        
        private static OAuthResponse RetryAccess(this OAuthRequest oAuthRequest, HttpWebRequest req, ref int retryCount)
        {
            try
            {
                return new OAuthResponse(req.GetResponse() as HttpWebResponse, retryCount);
            }
            catch (WebException ex)
            {
                if (ex.Status != WebExceptionStatus.ProtocolError)
                {
                    throw new Exception(ex.Message, ex);
                }

                HttpWebResponse res = ex.Response as HttpWebResponse;

                if (oAuthRequest.IgnoreStatusCodeCondition == null)
                {
                    throw new Exception(ex.Message, ex);
                }
                else if (oAuthRequest.IgnoreStatusCodeCondition(((int)res.StatusCode)))
                {
                    retryCount++;

                    if (retryCount > oAuthRequest.RetryCount)
                    {
                        return new OAuthResponse(res, false, retryCount - 1);
                    }

                    return oAuthRequest.RetryAccess(req, ref retryCount);
                }
                else
                {
                    throw new Exception(ex.Message, ex);
                }
            }
        }
    }
}
